1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.systems.gamepad;
12 
13 import hip.event.handlers.button;
14 public import hip.api.input.gamepad;
15 public import hip.math.vector;
16 import hip.systems.gamepads.xbox;
17 import hip.systems.gamepads.psv;
18 
19 
20 enum HipGamepadTypes : ubyte
21 {
22     xbox,
23     psvita
24 }
25 
26 HipGamepad getNewGamepad(ubyte type)
27 {
28     final switch(type)
29     {
30         case HipGamepadTypes.xbox: return new HipGamepad(new HipXBOXGamepad());
31         case HipGamepadTypes.psvita: return new HipGamepad(new HipPSVGamepad());
32     }
33 }
34 
35 interface IHipGamepadImpl
36 {
37     void poll(HipGamepad pad);
38     void setVibrating(ubyte id, 
39         double leftMotor, 
40         double rightMotor,
41         double leftTrigger,
42         double rightTrigger
43     );
44     bool isWireless(ubyte id);
45     HipGamepadBatteryStatus getBatteryStatus(ubyte id);
46 }
47 final class HipNullGamepad : IHipGamepadImpl
48 {
49     void poll(HipGamepad pad){}
50     void setVibrating(ubyte id, double leftMotor, double rightMotor, double leftTrigger, double rightTrigger){}
51     bool isWireless(ubyte id){return false;}
52     HipGamepadBatteryStatus getBatteryStatus(ubyte id){return HipGamepadBatteryStatus.init;}
53 }
54 
55 
56 private pragma(inline, true) float applyDeadzone(float input, float deadzone)
57 {
58     if(input < 0) return input > -deadzone ? 0 : input;
59     return input < deadzone ? 0 : input;
60 }
61 
62 private pragma(inline, true) float applyZones(float input, float deadzone, float alivezone)
63 {
64     input = applyDeadzone(input, deadzone);
65     if(input < 0) return input < -alivezone ? -1 : input;
66     return input > alivezone ? 1 : input;
67 }
68 
69 
70 /** Engine task:
71 * Send gamepad connect and disconnect events
72 * Poll every connected gamepad every frame
73 * Should always return a neutral state for every disconnected gamepad
74 *
75 * Game task:
76 * Check gamepad count
77 * Decide what will do with connected gamepads
78 */
79 class HipGamepad : AHipGamepad
80 {
81     HipGamepadBatteryStatus status;
82     Vector3 leftAnalog;
83     Vector3 rightAnalog;
84     float leftTrigger;
85     float rightTrigger;
86     ubyte id;
87     __gshared ubyte instanceCount = 0;
88     protected float vibrationAccumulator = 0;
89     protected HipButtonMetadata[HipGamepadButton.count] buttons;
90     private IHipGamepadImpl impl;
91 
92 
93 
94     ubyte getId(){return id;}
95     package this(IHipGamepadImpl impl)
96     {
97         id = instanceCount++;
98         for(int i = 0; i < buttons.length; i++)
99             buttons[i] = new HipButtonMetadata(i);
100         this.impl = impl;
101     }
102 
103     void setButtonPressed(HipGamepadButton btn, bool pressed){buttons[btn].setPressed(pressed);}
104 
105     void setAnalog(HipGamepadAnalogs analog, float[3] value)
106     {
107         value[0] = applyZones(value[0], deadZone, aliveZone);
108         value[1] = applyZones(value[1], deadZone, aliveZone);
109         value[2] = applyZones(value[2], deadZone, aliveZone);
110 
111         final switch(analog)
112         {
113             case HipGamepadAnalogs.leftStick:
114                 leftAnalog = value;
115                 break;
116             case HipGamepadAnalogs.rightStick:
117                 rightAnalog = value;
118                 break;
119             case HipGamepadAnalogs.rightTrigger:
120                 rightTrigger = value[0];
121                 break;
122             case HipGamepadAnalogs.leftTrigger:
123                 leftTrigger = value[0];
124                 break;
125         }
126     }
127     bool isButtonPressed(HipGamepadButton btn){return buttons[btn].isPressed;}
128     bool isButtonJustPressed(HipGamepadButton btn){return buttons[btn].isJustPressed;}
129     bool isButtonJustReleased(HipGamepadButton btn){return buttons[btn].isJustReleased;}
130 
131     bool areButtonsPressed(scope HipGamepadButton[] btns)
132     {
133         foreach(btn; btns)
134             if(!buttons[btn].isPressed) return false;
135         return true;
136     }
137     bool areButtonsJustPressed(scope HipGamepadButton[] btns)
138     {
139         foreach(btn; btns)
140             if(!buttons[btn].isJustPressed) return false;
141         return true;
142     }
143     bool areButtonsJustReleased(scope HipGamepadButton[] btns)
144     {
145         foreach(btn; btns)
146             if(!buttons[btn].isJustReleased) return false;
147         return true;
148     }
149 
150     void poll(float deltaTime)
151     {
152         if(vibrationTime != 0)
153         {
154             vibrationAccumulator+= deltaTime;
155             if(vibrationAccumulator >= vibrationTime)
156                 setVibrating(0,0);
157         }
158 
159         if(_isConnected) 
160             impl.poll(this);
161     }
162 
163     void postUpdate()
164     {
165         for(int i =0; i < buttons.length; i++)
166             buttons[i]._isNewState = false;
167     }
168 
169     final bool setVibrating(float time, double vibrationPower)
170     {
171         return setVibrating(time, vibrationPower,vibrationPower,vibrationPower,vibrationPower);
172     }
173 
174     bool setVibrating(float time,  double leftMotor,
175         double rightMotor,
176         double leftTrigger,
177         double rightTrigger
178     )
179     {
180         impl.setVibrating(getId, 
181             leftMotor,
182             rightMotor,
183             leftTrigger,
184             rightTrigger
185         );
186         vibrationAccumulator = 0;
187         vibrationTime = time;
188         return true;
189     }
190     
191     bool isWireless(){return impl.isWireless(getId);}
192     Vector3 getAnalogState(HipGamepadAnalogs analog)
193     {
194         final switch(analog)
195         {
196             case HipGamepadAnalogs.leftStick: return leftAnalog;
197             case HipGamepadAnalogs.rightStick: return rightAnalog;
198             case HipGamepadAnalogs.rightTrigger: return Vector3(rightTrigger, 0, 0);
199             case HipGamepadAnalogs.leftTrigger: return Vector3(leftTrigger, 0, 0);
200         }
201     }
202     float getBatteryStatus()
203     {
204         status = impl.getBatteryStatus(getId());
205         return cast(float)status.remainingCapacityInMilliwattHours
206             / status.fullChargeCapacityInMilliwattHours;
207     }
208 }
209 
210 void initGamepads()
211 {
212     import hip.systems.gamepads.xbox;
213     initXboxGamepadInput();
214 }